﻿using Microsoft.EntityFrameworkCore;
using Nantianmen.Core.Stores;
using Nantianmen.Domain.Models;
using Nantianmen.Shared;

namespace Nantianmen.EntityFrameworkCore
{
    public class ClusterStore : ClusterStoreBase, IClusterStore, IClusterDestinationStore, IQueryableClusterStore
    {
        public ClusterStore(DbContext context, NantianmenErrorDescriber? errorDescriber = null) : base(errorDescriber ?? new NantianmenErrorDescriber())
        {
            ArgumentNullException.ThrowIfNull(context, nameof(context));
            Context = context;
        }

        public virtual DbContext Context { get; }
        protected DbSet<Cluster> ClustersSet => Context.Set<Cluster>();
        protected DbSet<ClusterDestination> ClusterDestinations => Context.Set<ClusterDestination>();
        protected DbSet<Destination> DestinationSet => Context.Set<Destination>();

        public bool AutoSaveChanges { get; set; } = true;
        protected Task SaveChanges(CancellationToken cancellationToken)
        {
            return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.CompletedTask;
        }

        public override IQueryable<Cluster> Clusters => ClustersSet;

        public override async Task AddToDestinationAsync(Cluster cluster, string destinationId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNull(cluster);
            ArgumentNullException.ThrowIfNullOrWhiteSpace(destinationId);

            var clusterDestination = CreateClusterDestination(cluster, destinationId);
            var queryDuplicate = from item in ClusterDestinations
                                 where item.ClusterId == clusterDestination.ClusterId && item.DestinationId == clusterDestination.DestinationId
                                 select item;

            var duplicate = (await queryDuplicate.FirstOrDefaultAsync(cancellationToken));

            if (duplicate == null)
            {
                ClusterDestinations.Add(clusterDestination);
            }
        }

        public override async Task<bool> CheckDuplicateClusterIdAsync(string id, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNullOrWhiteSpace(id);

            var query = from cluster in ClustersSet
                        where cluster.Id == id
                        select cluster;

            return (await query.CountAsync(cancellationToken)) > 0;
        }

        public override async Task<NantianmenResult> CreateAsync(Cluster cluster, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNull(cluster);

            Context.Add(cluster);
            await SaveChanges(cancellationToken);
            return NantianmenResult.Success;
        }

        public override async Task<List<Cluster>> GetClustersAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            var query = Clusters;

            return await query.ToListAsync(cancellationToken);
        }

        public override async Task<bool> HasDestinationAsync(Cluster cluster, string destinationId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNull(cluster);
            ArgumentNullException.ThrowIfNullOrWhiteSpace(destinationId);

            var clusterDestinations = await FindClusterDestinationAsync(cluster.Id, destinationId, cancellationToken);

            return clusterDestinations.Count > 0;
        }

        public override async Task RemoveFromDestinationAsync(Cluster cluster, string destinationId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            var entryList = await FindClusterDestinationAsync(cluster.Id, destinationId, cancellationToken);
            var entry = entryList.FirstOrDefault();

            if (entry != null)
            {
                ClusterDestinations.Remove(entry);
            }
        }

        private async Task<List<ClusterDestination>> FindClusterDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);
            ArgumentNullException.ThrowIfNullOrWhiteSpace(destinationId);

            var query = from clusterDestination in ClusterDestinations
                        where clusterDestination.ClusterId == clusterId && clusterDestination.DestinationId == destinationId
                        select clusterDestination;

            return await query.ToListAsync(cancellationToken);
        }

        public override async Task<NantianmenResult> UpdateAsync(Cluster cluster, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            ArgumentNullException.ThrowIfNull(cluster);

            Context.Attach(cluster);
            Context.Update(cluster);

            await SaveChanges(cancellationToken);
            return NantianmenResult.Success;
        }

        public override async Task<bool> CheckDuplicateClusterDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);
            ArgumentNullException.ThrowIfNullOrWhiteSpace(destinationId);

            var clusterDestinations = await FindClusterDestinationAsync(clusterId, destinationId, cancellationToken);

            return clusterDestinations.Count > 0;
        }

        public override Task<Cluster?> FindClusterByIdAsync(string clusterId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);

            return ClustersSet.FindAsync(clusterId, cancellationToken).AsTask();
        }

        public override async Task<NantianmenResult> RemoveAsync(Cluster cluster, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            ArgumentNullException.ThrowIfNull(cluster);

            Context.Remove(cluster);

            await SaveChanges(cancellationToken);

            return NantianmenResult.Success;
        }

        public override Task<List<Destination>> FindClusterDestinationsAsync(string clusterId, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);

            var query = from clusterDestination in ClusterDestinations
                        where clusterDestination.ClusterId == clusterId
                        join destination in DestinationSet on clusterDestination.DestinationId equals destination.Id
                        select destination;

            return query.ToListAsync();
        }
    }
}
